--[[
	Describes an auction scan task to be performed.
	
	Copyright (C) Udorn (Blackhand)
	
	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.	
--]]

vendor.ScanTask = {}
vendor.ScanTask.prototype = {}
vendor.ScanTask.metatable = {__index = vendor.ScanTask.prototype}

local L = vendor.Locale.GetInstance()

local SCAN_PAGE_NONE = 0; -- not interessted in scanning pages
local SCAN_PAGE_WAIT = 1; -- waiting to scan next page
local SCAN_PAGE_PERFORM = 2; -- got permission to scan

--[[
	Updates the knowledge about the current scan, like num items and batches.
--]]
local function _UpdateScanStatus(self)
	self.numBatchAuctions, self.totalAuctions = GetNumAuctionItems("list");
	self.maxPages = math.ceil(self.totalAuctions / NUM_AUCTION_ITEMS_PER_PAGE);
	if (self.maxPages > 0 and self.page < self.maxPages and self.page > 0) then
		self.secPerPage = (GetTime() - self.startedAt) / self.page;
		if (self.page > 1) then
			self.restTime = SecondsToTime((self.maxPages - self.page) * self.secPerPage);
		end
	end
end

--[[
	Displays information about the scan progress to the user.
--]]
local function _ShowScanStatus(self)
	if (self.restTime) then
		vendor.Scanner:ShowScanStatus(L["Scan page %d/%d\nPer page %.2f sec\nEstimated time remaining: %s"]:format(self.page, self.maxPages, self.secPerPage, self.restTime));
	elseif (self.page and self.maxPages) then
		vendor.Scanner:ShowScanStatus(L["Scan page %d/%d"]:format(self.page, self.maxPages));
	end
end

--[[
	Blocks until the permission to send a query is given.
--]]
local function _WaitForPermission(self)
	local queryAllowed;
	repeat
		queryAllowed = vendor.Scanner:MaySendAuctionQuery();
		if (not queryAllowed) then
			coroutine.yield(); -- prevent from busy waiting
		end
	until queryAllowed;
end

--[[
	Waits until the owner is available or the the timeout time is reached. Then
	the empty string will be returned for the owner.
--]]
local function _WaitForOwner(index, timeoutTime)	
	while true do
		local _, _, _, _, _, _, _, _, _, _, _, owner = GetAuctionItemInfo("list", index);
		if (owner) then
			return owner;
		end
		if (GetTime() >= timeoutTime) then
			vendor.Vendor:Debug("owner not found");
			return "";
		end
		coroutine.yield(); -- continue waiting
	end
end

--[[ 
	Reads in the list of auction items.
--]]
local function _ReadPage(self)
	local numBatchAuctions = GetNumAuctionItems("list");
	for index = numBatchAuctions, 1, -1 do
		local itemLink = GetAuctionItemLink("list", index);
		local timeLeft = GetAuctionItemTimeLeft("list", index);
		if (itemLink) then
			local name, texture, count, quality, canUse, level, 
			minBid, minIncrement, buyoutPrice, bidAmount, 
			highBidder, owner = GetAuctionItemInfo("list", index);
			if (quality >= self.minQuality) then
				if (not owner) then
					if (self.name) then
						-- item scan has the time to wait
						owner = _WaitForOwner(index, GetTime() + 5);
					else
						-- complete scan has to be fast
						owner = "";
					end
				end
				if (not self.name or self.name == name) then
					local bid = minBid
					if (bidAmount > 0) then
						bid = bidAmount + minIncrement
					end
					vendor.Scanner:NotifyItemScanned(index, itemLink, name, count, bid, buyoutPrice, self.isFastScan, highBidder);
					self.snapshot:AddScanResult(itemLink, timeLeft, count, minBid, minIncrement, buyoutPrice, bidAmount, owner);
				end
			end
		end
	end
end

--[[
	Blocks until the current page may be scanned.
--]]
local function _WaitForScan(self)
	while (self.scanPage ~= SCAN_PAGE_NONE) do
		if (self.scanPage < SCAN_PAGE_PERFORM) then
			coroutine.yield(); -- prevent from busy waiting
		else
			_ReadPage(self);
			self.scanPage = SCAN_PAGE_NONE;
		end
	end
end

--[[ 
	Creates a new ScanTask instance.
	@param itemLink selects the item to be scanned, nil for any.
	@param minQuality the minimal quality for items to be accepted. 0 is grey.
--]]
local function _Init(self, itemLink, minQuality, isFastScan)
	self.itemLink = itemLink;
	if (itemLink) then
		self.name = GetItemInfo(itemLink);
	end
	self.minQuality = minQuality;
	self.page = nil;
	self.isRunning = true;
	self.isCancelled = false;
	self.scanPage = SCAN_PAGE_NONE;
	self.isNeutral =  vendor.AuctionHouse:IsNeutral();
	self.snapshot = vendor.ScanSnapshot:new();
	self.isFastScan = isFastScan
	if (self.isFastScan) then
		vendor.Vendor:Debug("ISFASTSCAN")
	end
end

--[[ 
	Creates a new instance.
--]]
function vendor.ScanTask:new(itemLink, minQuality, isFastScan)
	local instance = setmetatable({}, self.metatable)
	_Init(instance, itemLink, minQuality, isFastScan)
	return instance
end

--[[
	Run function of the task, performs the scan.
--]]
function vendor.ScanTask.prototype:Run()
	vendor.Scanner:NotifyStartScan()
	self.startedAt = GetTime();
	-- we scan backwards to handle buyout of items, so it should be sorted backward
	SortAuctionItems("list", "buyout");	
	local sorted = IsAuctionSortReversed("list", "buyout");
	if (sorted) then
		SortAuctionItems("list", "buyout");
	end
	-- now do the scan
	while (self.isRunning) do
		_WaitForScan(self);
		if (not self.page) then
			self.page = 0;
		else
			self.page = self.page + 1;
		end
		_UpdateScanStatus(self);
		if (self.page > 0 and not self.name) then
			_ShowScanStatus(self);
		end		
		if (self.page > 0 and self.page >= self.maxPages) then
			vendor.Vendor:Debug("leave page: "..self.page.." maxPages: "..self.maxPages);
			self.isRunning = false;
		else
			_WaitForPermission(self);
			self.scanPage = SCAN_PAGE_WAIT; -- has to scan this page, before we can continue
			--QueryAuctionItems(self.name, "", "", nil, nil, nil, self.page, nil, nil);
			local name = self.name
			if (name) then
				-- too long names may cause a disconnect
				name = string.sub(name, 1, 62)
			end
			QueryAuctionItems(name, "", "", nil, nil, nil, self.page, nil, nil);
		end
	end
	if (not self.name) then
		vendor.Scanner:ShowScanStatus(L["Finished scan"]);
	end
	-- inform the scanner
	if (not self.name) then
		-- complete scan
		if (not self.isCancelled) then
			vendor.Scanner:SetSnapshot(self.snapshot, self.isNeutral);
		else
			vendor.Vendor:Debug("cancelled complete scan");
			vendor.Scanner:AbandonScan();
		end
	else
		-- item scan
		if (not self.isCancelled) then
			vendor.Scanner:SetItemSnapshot(self.itemLink, self.snapshot, self.isNeutral);
		else
			vendor.Vendor:Debug("cancelled item scan");
			vendor.Scanner:AbandonScan();
		end
	end
end

--[[
	Cancels the task and leaves it as soon as possible. 
--]]
function vendor.ScanTask.prototype:Cancel()
	self.isCancelled = true;
	self.isRunning = false;
end

--[[
	Returns whether the task was canecelled.
--]]
function vendor.ScanTask.prototype:IsCancelled()
	return self.isCancelled;
end

--[[ 
	Reads in the list of auction items, will be called by the Scanner.
--]]
function vendor.ScanTask.prototype:AuctionListUpdate()
	if (self.scanPage == SCAN_PAGE_WAIT) then
		self.scanPage = SCAN_PAGE_PERFORM;
	end	
end

--[[
	Will be called by the TaskQueue, if the task has failed with an 
	unexpected error.
--]]
function vendor.ScanTask.prototype:Failed()
	vendor.Vendor:Debug("ScanTask has failed");
	self:Cancel();
	vendor.Scanner:AbandonScan();
end
